﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;

#if !DOT_NET
using Windows.Foundation;

#endif

namespace StyleMVVM.Configuration
{
	public sealed class ConfigurationService : IConfigurationService
	{
		private static readonly string supplemental = typeof(ConfigurationService).FullName;
		private static readonly object lockObject = new object();
		private static volatile WeakReference instance;
		private List<IConfigurationStorageProvider> providersList;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			container.Register(typeof(ConfigurationService)).
			          As(typeof(IConfigurationService)).
			          CoreExport(CoreExportStartOrder.High).
			          ImportMethod("ImportProviders").
			          AndSharedPermenantly();
		}

		public ConfigurationService()
		{
		}

		public void ImportProviders(IDependencyInjectionContainer container)
		{
			IEnumerable<object> providers = container.LocateAllByType(typeof(IConfigurationStorageProvider));

			providersList = new List<IConfigurationStorageProvider>();

			foreach (object provider in providers)
			{
				if (provider is IConfigurationStorageProvider)
				{
					providersList.Add(provider as IConfigurationStorageProvider);
				}
			}
		}

		public static IConfigurationService Instance
		{
			get
			{
				IConfigurationService returnValue = null;

				if (instance != null)
				{
					returnValue = instance.Target as IConfigurationService;
				}

				if (returnValue == null)
				{
					lock (lockObject)
					{
						if (instance == null || instance.Target == null)
						{
							instance = new WeakReference(
								Bootstrapper.Instance.Container.LocateByType(typeof(IConfigurationService)));
						}

						returnValue = instance.Target as IConfigurationService;
					}
				}

				return returnValue;
			}
		}

#if NETFX_CORE
		public IAsyncOperation<object> GetValue(string name, Type returnType)
		{
			return InternalGetValue(name, returnType).AsAsyncOperation();
		}

		public IAsyncOperation<object> GetValue(string name, Type returnType, object defaultValue)
		{
			return InternalGetValue(name, returnType, defaultValue).AsAsyncOperation();
		}

		public IAsyncOperation<bool> SetValue(string name, object newValue)
		{
			return InternalSetValue(name, newValue).AsAsyncOperation();
		}

		public IAsyncOperation<object> GetUserValue(string userName, string valueName, Type returnType)
		{
			return InternalGetUserValue(userName, valueName, returnType).AsAsyncOperation();
		}

		public IAsyncOperation<object> GetUserValue(string userName, string valueName, Type returnType, object defaultValue)
		{
			return InternalGetUserValue(userName, valueName, returnType, defaultValue).AsAsyncOperation();
		}

		public IAsyncOperation<bool> SetUserValue(string userName, string valueName, object newValue)
		{
			return InternalSetUserValue(userName, valueName, newValue).AsAsyncOperation();
		}
#else
		public Task<object> GetValue(string name, Type returnType)
		{
			return InternalGetValue(name, returnType);
		}

		public Task<object> GetValue(string name, Type returnType, object defaultValue)
		{
			return InternalGetValue(name, returnType, defaultValue);
		}

		public Task<bool> SetValue(string name, object newValue)
		{
			return InternalSetValue(name, newValue);
		}

		public Task<object> GetUserValue(string userName, string valueName, Type returnType)
		{
			return InternalGetUserValue(userName, valueName, returnType);
		}

		public Task<object> GetUserValue(string userName, string valueName, Type returnType, object defaultValue)
		{
			return InternalGetUserValue(userName, valueName, returnType, defaultValue);
		}

		public Task<bool> SetUserValue(string userName, string valueName, object newValue)
		{
			return InternalSetUserValue(userName, valueName, newValue);
		}
#endif

		internal async Task<object> InternalGetValue(string name, Type returnType)
		{
			List<Exception> exceptionList = null;

			foreach (IConfigurationStorageProvider provider in providersList)
			{
				try
				{
					ConfigValuePair returnValue = await provider.GetValue(name, returnType);

					if (returnValue != null && returnValue.ReturnValue)
					{
						return returnValue.ConfigValue;
					}
				}
				catch (Exception exp)
				{
					if (exceptionList == null)
					{
						exceptionList = new List<Exception>();
					}

					exceptionList.Add(exp);

					Logger.Error("Exception thrown while getting value", supplemental, exp);
				}
			}

			string exceptionString = "Could not find value: " + name;

			if (exceptionList != null)
			{
				throw new KeyNotFoundException(exceptionString, new AggregateException(exceptionList));
			}

			throw new KeyNotFoundException(exceptionString);
		}

		internal async Task<object> InternalGetValue(string name, Type returnType, object defaultValue)
		{
			foreach (IConfigurationStorageProvider provider in providersList)
			{
				try
				{
					ConfigValuePair returnValue = await provider.GetValue(name, returnType);

					if (returnValue.ReturnValue)
					{
						return returnValue.ConfigValue;
					}
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while getting value", supplemental, exp);
				}
			}

			return defaultValue;
		}

		internal async Task<bool> InternalSetValue(string name, object newValue)
		{
			List<Exception> exceptionList = null;
			bool saved = false;

			foreach (IConfigurationStorageProvider provider in providersList)
			{
				if (provider.CanSet)
				{
					try
					{
						saved = await provider.SetValue(name, newValue);

						if (saved)
						{
							break;
						}
					}
					catch (Exception exp)
					{
						if (exceptionList == null)
						{
							exceptionList = new List<Exception>();
						}

						exceptionList.Add(exp);

						Logger.Error("Exception thrown while setting value", supplemental, exp);
					}
				}
			}

			if (!saved && exceptionList != null)
			{
				throw new AggregateException("There were exceptions thrown during SetValue", exceptionList);
			}

			return saved;
		}

		internal async Task<object> InternalGetUserValue(string userName, string valueName, Type returnType)
		{
			List<Exception> exceptionList = null;

			foreach (IConfigurationStorageProvider provider in providersList)
			{
				try
				{
					ConfigValuePair returnValue = await provider.GetUserValue(userName, valueName, returnType);

					if (returnValue.ReturnValue)
					{
						return returnValue.ConfigValue;
					}
				}
				catch (Exception exp)
				{
					if (exceptionList == null)
					{
						exceptionList = new List<Exception>();
					}

					exceptionList.Add(exp);

					Logger.Error("Exception thrown while getting user value", supplemental, exp);
				}
			}

			string exceptionString = string.Format("Could not find value: {0} for user {1}", valueName, userName);

			if (exceptionList != null)
			{
				throw new KeyNotFoundException(exceptionString, new AggregateException(exceptionList));
			}

			throw new KeyNotFoundException(exceptionString);
		}

		internal async Task<object> InternalGetUserValue(string userName,
		                                                 string valueName,
		                                                 Type returnType,
		                                                 object defaultValue)
		{
			foreach (IConfigurationStorageProvider provider in providersList)
			{
				try
				{
					ConfigValuePair returnValue = await provider.GetUserValue(userName, valueName, returnType);

					if (returnValue.ReturnValue)
					{
						return returnValue.ConfigValue;
					}
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while getting value", supplemental, exp);
				}
			}

			return defaultValue;
		}

		internal async Task<bool> InternalSetUserValue<T>(string userName, string valueName, T newValue)
		{
			List<Exception> exceptionList = null;
			bool saved = false;

			foreach (IConfigurationStorageProvider provider in providersList)
			{
				if (provider.CanSet)
				{
					try
					{
						saved = await provider.SetUserValue(userName, valueName, newValue);

						if (saved)
						{
							break;
						}
					}
					catch (Exception exp)
					{
						if (exceptionList == null)
						{
							exceptionList = new List<Exception>();
						}

						exceptionList.Add(exp);

						Logger.Error("Exception thrown while setting value", supplemental, exp);
					}
				}
			}

			if (!saved && exceptionList != null)
			{
				throw new AggregateException("There were exceptions thrown during SetValue", exceptionList);
			}

			return saved;
		}
	}
}